package flatlandAgent;

import neuralNetwork.NeuralNetwork;
import neuralNetwork.NeuralNetwork.ArrayOperations;
import evolutionaryProblems.Phenotype;
import fitnessFunctions.FitnessFunction;
import genotype.FlatlandGenotype;

public class FlatlandFitnessFunction implements FitnessFunction {
	
	private FlatlandParameters parameters; 
	private FlatlandMap[] maps; 
	private int nofFood = 0, nofPoison = 0; 
	
	public FlatlandFitnessFunction(FlatlandParameters parameters){
		this.parameters = parameters; 
		generateMaps(); 
	}
	
	private void generateMaps(){
		int nofMaps = parameters.getNofMaps(); 
		maps = new FlatlandMap[nofMaps]; 
		for (int i = 0; i < nofMaps; i++) {
			FlatlandMap map = new FlatlandMap(parameters.getFoodDist(), parameters.getPoisonDist()); 
			maps[i] = map;
			nofFood += map.getCountFood(); 
			nofPoison += map.getCountPoison(); 
		}
	}

	@Override
	public double calculateFitness(Phenotype phenotype) {
		int countFood = 0; 
		int countPoison = 0; 
		for (FlatlandMap map : maps) {
//			long start = System.currentTimeMillis(); 
			int countFoodMap = 0; 
			int countPoisonMap = 0; 
			FlatlandGame game = new FlatlandGame(map); 
			game.setDraw(false); 
			if (parameters.isViewMap()) parameters.getGui().setMap(game.getGamePanel());
			Double[] weights = ((FlatlandGenotype)phenotype.getGenotype()).getArrayGenotype(); 
			NeuralNetwork network = new NeuralNetwork(parameters.getNetworkLayout());
			network.setWeights(convertDtod(weights)); 
//			long end = System.currentTimeMillis(); 
//			System.out.println("Time: " + (end-start));

			int iterations = parameters.getIterationsPerMap();
//			long iterationsStart = System.currentTimeMillis(); 
			for (int i = 0; i < iterations; i ++){
				boolean[] foodSensors = game.getFoodSensors();
				boolean[] poisonSensors = game.getPoisonSensors(); 
				boolean[] sensors = new boolean[foodSensors.length + poisonSensors.length]; 
				int foodIndex = 0; 
				int poisonIndex = foodSensors.length; 
				ArrayOperations.insertData(sensors, foodSensors, foodIndex); 
				ArrayOperations.insertData(sensors, poisonSensors, poisonIndex); 
				double[] outputs = network.update(sensors); 
				FlatlandModel.Action action = selectAction(outputs); 
				
				int moveResult = game.move(action); 
				if (moveResult > 0){
					countFoodMap++; 
				} else if (moveResult < 0){
					countPoisonMap ++; 
				}
			}
//			long iterationsEnd = System.currentTimeMillis(); 
//			System.out.println("Iterations time: " + (iterationsEnd-iterationsStart));
			countFood += countFoodMap; 
			countPoison += countPoisonMap; 
		}
		double fitness = ((double)(countFood - countPoison))/nofFood; 
		if (fitness < 0 ){
			return 0; 
		} else {
			return fitness; 
		}
	}
	
	public void showPhenotype(Phenotype phenotype){
		for (FlatlandMap map : maps) {
//			long start = System.currentTimeMillis(); 
			FlatlandGame game = new FlatlandGame(map); 
			game.setDraw(true);
			parameters.getGui().setMap(game.getGamePanel());
			Double[] weights = ((FlatlandGenotype)phenotype.getGenotype()).getArrayGenotype(); 
			NeuralNetwork network = new NeuralNetwork(parameters.getNetworkLayout());
			network.setWeights(convertDtod(weights)); 
//			long end = System.currentTimeMillis(); 
//			System.out.println("Time: " + (end-start));

			int iterations = parameters.getIterationsPerMap();
//			long iterationsStart = System.currentTimeMillis(); 
			for (int i = 0; i < iterations; i ++){
				boolean[] foodSensors = game.getFoodSensors();
				boolean[] poisonSensors = game.getPoisonSensors(); 
				boolean[] sensors = new boolean[foodSensors.length + poisonSensors.length]; 
				int foodIndex = 0; 
				int poisonIndex = foodSensors.length; 
				ArrayOperations.insertData(sensors, foodSensors, foodIndex); 
				ArrayOperations.insertData(sensors, poisonSensors, poisonIndex); 
				double[] outputs = network.update(sensors); 
				FlatlandModel.Action action = selectAction(outputs); 
				try {
					Thread.sleep(parameters.getTimePerFrame());
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				game.move(action); 
			}
//			long iterationsEnd = System.currentTimeMillis(); 
//			System.out.println("Iterations time: " + (iterationsEnd-iterationsStart));
		}
	}
	
	private FlatlandModel.Action selectAction(double[] outputs){
		int maxIndex = 0; 
		double max = outputs[0]; 
		for (int i = 0; i < outputs.length; i++){
			if (outputs[i] > max){
				maxIndex = i; 
				max = outputs[i]; 
			}
		}
		switch (maxIndex) {
		case 0:
			return FlatlandModel.Action.MOVE_LEFT;
		case 1: 
			return FlatlandModel.Action.MOVE_FORWARD; 
		default:
			return FlatlandModel.Action.MOVE_RIGHT;
		}
	}
	
	public double[] convertDtod(Double[] list){
		double[] convert = new double[list.length]; 
		for (int i = 0; i < convert.length; i++) {
			convert[i] = list[i]; 
		}
		return convert; 
	}

	@Override
	public void nextGeneration() {
		if (parameters.isDynamicMap()) {
			this.parameters.setFitnessFunction(new FlatlandFitnessFunction(parameters)); 
		}
		
	}
	
	public FlatlandMap[] getMaps(){
		return maps; 
	}
	
	

}
